// 游戏房间管理模块文档的说明：

//     1.对匹配成功的玩家创建房间，建立起一个小范围的玩家之间的关联关系房间里一个玩家产生的动作会广播给房间里的其他用户
//     2.因为房间有可能会有很多，因此需要将这些房间管理起来以便于对于房间生命周期的控制
// 实现两个部分:
//     游戏房间:
//     1.房间的设计    
//     1.管理房间中的数据
//     游戏房间的设计:
// 管理的数据:
//     1.房间的ID
//     2.房间的状态（决定了一个玩家退出房间时所做的动作)
//     3.房间中玩家的数量（决定了房间什么时候销毁)
//     4.白棋玩家ID
//     5.黑棋玩家ID
//     6.用户信息表的句柄（当玩家胜利/失败时更新用户数据)
//     7.棋盘信息(二维数组)
// 房间中产生的动作:
//     1.下棋
//     2.聊天
// 不管是哪个动作，只要是合理的，都要广播给房间里的其他用户


// typedef enum{
//     GAME_START,
//     GAME_OVER
// }room_statu;

// class room{
// private:
//     uint64_t _room_id;
//     room_statu _statu;
//     int _player_count;
//     uint64_t _white_id;
//     uint64_t _black_id;
//     user_table *_tb_user;
//     online_manager *_online_user;
//     std::vector<std::vector<int>> _board;
// public:
//     room(){}
//     ~room(){}
//     // 处理下棋的动作
//     Json::Value handle_chess(Json::Value &req);
//     // 处理聊天的动作
//     Json::Value handle_chat(Json::Vallue &req);
//     // 处理玩家退出房间的动作
//     void handle_exit(uint64_t uid);
//     // 总的请求处理函数，在函数内部，区分请求类型，根据不同的请求调用不同的处理函数，得到响应进行广播
//     void handle_request(Json::Value &req);
//     // 将指定的信息广播给房间中的所有玩家
//     void broadcast(Json::Value &rep);
// };



#ifndef __M_ROOM_H__
#define __M_ROOM_H__
#include "util.hpp"
#include "online.hpp"   // 结合了 online.hpp
#include "db.hpp"       // 结合了 db.hpp

#define BOARD_ROW 15
#define BOARD_COL 15
// 棋子的颜色
#define CHESS_WHITE 1
#define CHESS_BLACK 2

typedef enum{
    GAME_START,
    GAME_OVER
}room_statu;

class room{
private:
    uint64_t _room_id;   // 外部传入
    // 内部直接初始化
    room_statu _statu;   
    int _player_count;
    uint64_t _white_id;
    uint64_t _black_id;

    user_table *_tb_user;                  // 外部传入
    online_manager *_online_user;          // 外部传入
    std::vector<std::vector<int>> _board;  // 设置为 15 * 15

// 下面的判断五星连珠的写法也是可以直接定义8个反向的偏移量数组来遍历的，后面再修改
private:
    bool five(int row, int col, int row_off, int col_off, int color){
        // row和col是下棋位置，  row_off和col_off是偏移量，也是方向
        int count = 1;
        
        // 一个方向走
        int search_row = row + row_off;
        int search_col = col + col_off;
        while(search_row >= 0 && search_row < BOARD_ROW &&
                search_col >= 0 && search_col < BOARD_COL &&
                _board[search_row][search_col] == color) {
            //  同色棋子数量++
            count++;
            //  检索位置继续向后偏移
            search_row += row_off;
            search_col += col_off;
        }

        // 另一个方向走
        search_row = row - row_off;
        search_col = col - col_off;
        while(search_row >= 0 && search_row < BOARD_ROW &&
                    search_col >= 0 && search_col < BOARD_COL &&
            _board[search_row][search_col] == color) {
            //  同色棋子数量++
            count++;
            //  检索位置继续向后偏移
            search_row -= row_off;
            search_col -= col_off;
        }

        return (count >= 5);
    }

    uint64_t check_win(int row, int col, int color){
        // 从下棋位置的5个不同方向上检测是否出现了5个及以上相同颜色的棋子（横行，纵列，正斜，反斜）
        if(five(row, col, 0, 1, color) ||
            five(row, col, 1, 0, color) || 
            five(row, col, -1, 1, color) ||
            five(row, col, -1, -1, color)){
            // 任意一个方向上出现了true也就是五星连珠，则设置返回值
            return color == CHESS_WHITE ? _white_id : _black_id;
        }

        return 0;
    }

public:
    room(uint64_t &room_id, user_table* tb_user, online_manager* online_user)
        : _room_id(room_id)
        , _statu(GAME_START)
        , _player_count(0)
        , _tb_user(tb_user)
        , _online_user(online_user)
        , _board(BOARD_ROW, std::vector<int>(BOARD_COL, 0)){
        DLOG("%lu 房间创建成功!", _room_id);
    }

    ~room(){
        DLOG("%lu 房间销毁成功!", _room_id);
    }

    // 周边小函数
    uint64_t id(){return _room_id; }
    room_statu statu(){return _statu; }
    int player_count(){return _player_count; }
    void add_white_user(uint64_t uid){_white_id = uid; _player_count++;}
    void add_black_user(uint64_t uid){_black_id = uid; _player_count++;}
    uint64_t get_white_user(){return _white_id; }
    uint64_t get_black_user(){return _black_id; }

    // {
    //     "optype": "put_chess", // put_chess表⽰当前请求是下棋操作
    //     "room_id": 222, // room_id 表⽰当前动作属于哪个房间
    //     "uid": 1, // 当前的下棋操作是哪个⽤⼾发起的
    //     "row": 3, // 当前下棋位置的⾏号
    //     "col": 2 // 当前下棋位置的列号
    // }
    // {
    //     "optype": "put_chess",
    //     "result": false
    //     "reason": "⾛棋失败具体原因...."
    // }
    // {
    //     "optype": "put_chess",
    //     "result": true,
    //     "reason": "对⽅掉线，不战⽽胜！" / "对⽅/⼰⽅五星连珠，战⽆敌/虽败犹荣！",
    //     "room_id": 222,
    //     "uid": 1,
    //     "row": 3,
    //     "col": 2,
    //     "winner": 0 // 0-未分胜负， !0-已分胜负 (uid是谁，谁就赢了)
    // }
    // 处理下棋的动作
    Json::Value handle_chess(Json::Value &req){
        // 1. 当前请求的 room_id 是否与 _room_id 匹配
        // 2. 判断两个玩家石佛偶都在线，任意一个玩家不在线的情况，那么直接另一个玩家胜利
        // 3. 获取走棋位置，判断当前走棋是否合理（位置是否已经被占用）
        // 4. 判断是否有玩家胜利（从当前走棋位置开始判断是否存在五星连珠）
    
        // 只要有问题的话，就组织 resp 返回了
        Json::Value json_resp = req;  
        // 1. 当前请求的 room_id 是否与 _room_id 匹配
        // uint64_t room_id = req["room_id"].asUInt64();
        // if(room_id != _room_id){
        //     // 组织信息返回
        //     json_resp["optype"] = "put_chess";
        //     json_resp["result"] = false;
        //     json_resp["reason"] = "房间号不匹配!";
        //     return json_resp;
        // }
        // 2. 判断两个玩家石佛偶都在线，任意一个玩家不在线的情况，那么直接另一个玩家胜利
        // 棋盘的大小在上面先获取也是可以的，防止后面还要用的时候再取
        int chess_row = req["row"].asInt();
        int chess_col = req["col"].asInt();
        uint64_t cur_uid = req["uid"].asUInt64();
        if(_online_user->is_in_game_room(_white_id) == false){
            // json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "运气真好！对方掉线，不战而胜！";
            // json_resp["room_id"] = (Json::UInt64)_room_id;
            // json_resp["uid"] = (Json::UInt64)cur_uid;
            // json_resp["row"] = chess_row;
            // json_resp["col"] = chess_col;
            json_resp["winner"] = (Json::UInt64)_black_id;  // 上面判断 _white_id == false 下线了 _black_id 自然胜利了
            
            // 更新数据库的用户信息
            // uint64_t loser_id = _white_id;    // 上面判断 _white_id == false 下线了 _black_id 自然胜利了
            // _tb_user->win(_black_id);
            // _tb_user->lose(loser_id);

            return json_resp;
        }
        if(_online_user->is_in_game_room(_black_id) == false){
            // json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "运气真好！对方掉线，不战而胜！";
            // json_resp["room_id"] = (Json::UInt64)_room_id;
            // json_resp["uid"] = (Json::UInt64)cur_uid;
            // json_resp["row"] = chess_row;
            // json_resp["col"] = chess_col;
            json_resp["winner"] = (Json::UInt64)_white_id;  // 上面判断 _black_id == false 下线了 _white_id 自然胜利了
            
            // 更新数据库的用户信息
            // uint64_t loser_id = _black_id;    // 上面判断 _black_id == false 下线了 _white_id 自然胜利了
            // _tb_user->win(_white_id);
            // _tb_user->lose(loser_id);
            
            return json_resp;
        }
        // 3. 获取走棋位置，判断当前走棋是否合理（位置是否已经被占用）
        if(_board[chess_row][chess_col] != 0){
            // 组织信息返回
            // json_resp["optype"] = "put_chess";
            json_resp["result"] = false;
            json_resp["reason"] = "当前位置已经有了其它的棋子!";
            return json_resp;
        }
        // 看是谁的棋
        int cur_color = cur_uid == _white_id ? CHESS_WHITE : CHESS_BLACK;
        _board[chess_row][chess_col] = cur_color;
        // 4. 判断是否有玩家胜利（从当前走棋位置开始判断是否存在五星连珠）
        uint64_t winner_id = check_win(chess_row, chess_col, cur_color);
        if(winner_id != 0){
            // 更新数据库的用户信息
            json_resp["reason"] = "五星连珠，战无敌！";
            // uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
            // _tb_user->win(winner_id);
            // _tb_user->lose(loser_id);
        }        

        // if(winner_id != 0){
            // json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            // json_resp["reason"] = "五星连珠，战无敌！";
            // json_resp["room_id"] = (Json::UInt64)_room_id;
            // json_resp["uid"] = (Json::UInt64)cur_uid;
            // json_resp["row"] = chess_row;
            // json_resp["col"] = chess_col;
            json_resp["winner"] = (Json::UInt64)winner_id;  // winner 赢了
            return json_resp;
        // }
    }


    // {
    //     "optype": "chat",
    //     "room_id": 222,
    //     "uid": 1,
    //     "message": "赶紧点"
    // }
    // {
    //     "optype": "chat",
    //     "result": false
    //     "reason": "聊天失败具体原因....⽐如有敏感词..."
    // }
    // {
    //     "optype": "chat",
    //     "result": true,
    //     "room_id": 222,
    //     "uid": 1,
    //     "message": "赶紧点"
    // }
    // 处理聊天的动作
    Json::Value handle_chat(Json::Value &req){
        // 1. 检测房间号是否一致
        // 2. 检测消息中是否含有敏感词
        // 3. 广播消息

        Json::Value json_resp = req;
        // 1. 检测房间号是否一致
        // uint64_t room_id = req["room_id"].asUInt64();
        // if(room_id != _room_id){
        //     json_resp["result"] = false;
        //     json_resp["reason"] = "房间号不一致！";
        //     return json_resp;
        // }
        // 2. 检测消息中是否含有敏感词
        // 后面如果要再扩展的话，可以再封装函数的哦
        std::string msg = req["message"].asString();
        size_t pos = msg.find("垃圾");
        if(pos != std::string::npos){
            json_resp["result"] = false;
            json_resp["reason"] = "消息中包含敏感词不能发送！";
            return json_resp;   
        }
        // 3. 广播消息
        json_resp["result"] = true;  // 消息是通过的
        return json_resp;
    }
    
    // 处理玩家退出房间的动作
    void handle_exit(uint64_t uid){
        // 1. 如果是下棋中退出的话，则对方胜利，否则下棋结束了退出，则是正常退出的
        // 2. 房间中玩家的数量--

        // 1. 如果是下棋中退出的话，则对方胜利，否则下棋结束了退出，则是正常退出的
        Json::Value json_resp;
        if(_statu == GAME_START){
            uint64_t winner_id = (Json::UInt64)(uid == _white_id ? _black_id : _white_id);
            json_resp["optype"] = "put_chess";
            json_resp["result"] = true;
            json_resp["reason"] = "对方掉线，不战而胜！";
            json_resp["room_id"] = (Json::UInt64)_room_id;
            json_resp["uid"] = (Json::UInt64)uid;
            json_resp["row"] = -1;
            json_resp["col"] = -1;  
            // 下面的转一下，不然好像有问题。很奇怪感觉
            // json_resp["winner"] = uid == (Json::UInt64)_white_id ? (Json::UInt64)_black_id : (Json::UInt64)_white_id;
            json_resp["winner"] = (Json::UInt64)winner_id;
            uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
            _tb_user->win(winner_id);
            _tb_user->lose(loser_id);
            _statu = GAME_OVER;
            broadcast(json_resp);  // 广播一下
        }    
        // 2. 房间中玩家的数量--
        _player_count--;
        return;
    }
    
    // 总的请求处理函数，在函数内部，区分请求类型，根据不同的请求调用不同的处理函数，得到响应进行广播
    void handle_request(Json::Value &req){
        // 1. 校验房间号是否匹配
        // 2. 根据不同的请求类型调用不同的处理函数

        Json::Value json_resp;
        // 1. 校验房间号是否匹配
        uint64_t room_id = req["room_id"].asUInt64();
        if(room_id != _room_id){
            json_resp["optype"] = req["optype"].asString();
            json_resp["result"] = false;
            json_resp["reason"] = "房间号不一致！";
            return broadcast(json_resp);
        }
        // 2. 根据不同的请求类型调用不同的处理函数
        if(req["optype"].asString() == "put_chess"){
            json_resp = handle_chess(req);
            if(json_resp["winner"].asUInt64() != 0){
                uint64_t winner_id = json_resp["winner"].asUInt64();
                uint64_t loser_id = winner_id == _white_id ? _black_id : _white_id;
                _tb_user->win(winner_id);
                _tb_user->lose(loser_id);
                _statu = GAME_OVER;
            }
        }else if(req["optype"].asString() == "chat"){
            json_resp = handle_chat(req);
        }else{
            json_resp["optype"] = req["optype"].asString();
            json_resp["result"] = false;
            json_resp["reason"] = "未知请求类型";
        }
        std::string body;
        json_util::serialize(json_resp, body);
        DLOG("房间-广播动作: %s", body.c_str());
        return broadcast(json_resp);
    }
    
    // 将指定的信息广播给房间中的所有玩家
    void broadcast(Json::Value &rsp){
        // 1. 对要响应的信息进行序列化，将Json::Value中的数据序列化成为json格式字符串
        std::string body;
        json_util::serialize(rsp, body);
        // 2. 获取房间里面的所有在线用户
        // 3. 发送响应信息
        wsserver_t::connection_ptr wconn = _online_user->get_conn_from_room(_white_id);
        if(wconn.get() != nullptr){
            wconn->send(body);     // 库函数中的发送函数
        }else {
            DLOG("房间-白棋玩家连接获取失败");
            
            // 只有一个玩家了直接退出
            DLOG("房间只有白棋一个玩家了");
        }
        wsserver_t::connection_ptr bconn = _online_user->get_conn_from_room(_black_id);
        if(bconn.get() != nullptr){
            bconn->send(body);
        }else {
            DLOG("房间-黑棋玩家连接获取失败");

            // 只有一个玩家了直接退出
            DLOG("房间只有黑棋一个玩家了");
        }
        return;
    }
};



// 房间管理文档的说明:
//     1.创建房间（两个玩家对战匹配完成了，为他们创建一个房间，需要传入两个玩家的用户ID)
//     2.查找房间（通过房间ID查找房间信息;通过用户ID查找所在房间信息)
//     3.销毁房间（根据房间ID销毁房间;房间中所有用户都退出了，销毁房间)需要管理的数据:
    
//     1.数据管理模块句柄;
//     2.在线用户管理模块句柄;
//     3.房间ID分配计数器;
//     4.互斥锁
//     using room_ptr = stdshared_ptr<room>;房间信息的空间使用shared_ptr进行管理
//     5.unorderedmap<room_rid，room_ptr>房间信息管理（建立起房间ID与房间信息的映射关系)
//     6.unorderedmap<uid, rid>房间ID与用户ID的关联关系管理（先通过用户ID找到所在房间ID，再去查找房间信息)

// using room_ptr = std::shared_ptr<room>;

// class room_manager{
// private:
//     uint64_t _next_rid;
//     std::mutex _mutex;
//     user_table* _tb_user;
//     online_manager* _online_user;
//     std::unordered_map<uint64_t, room_ptr> _rooms;
//     std::unordered_map<uint64_t, uint64_t> _users;
// public:
//     room_manager(user_table* ut, online_manager* om){}
//     ~room_manager(){}

//     // 为两个用户创建房间，并返回房间的智能指针管理对象
//     room_ptr create_room(uint64_t uid1, uint64_t uid2);
//     // 通过房间id获取房间信息
//     room_ptr get_room_by_rid(uint64_t rid);
//     // 通过用户id获取房间信息
//     room_ptr get_room_by_uid(uint64_t uid);
//     // 移除房间
//     void remove_room(uint64_t rid);
//     // 移除房间中的指定用户。如果没有用户了则销毁房间，用户连接断开时被调用
//     void remove_room_user(uint64_t uid);
// };



using room_ptr = std::shared_ptr<room>;

class room_manager{
private:
    uint64_t _next_rid;
    std::mutex _mutex;
    user_table* _tb_user;
    online_manager* _online_user;
    std::unordered_map<uint64_t, room_ptr> _rooms;
    std::unordered_map<uint64_t, uint64_t> _users;
public:
    room_manager(user_table* ut, online_manager* om)
        :_next_rid(1)
        , _tb_user(ut)
        , _online_user(om)
    {
        DLOG("房间模块初始化完毕！");
    }
    ~room_manager(){
        DLOG("房间模块即将销毁！");
    }

    // 为两个用户创建房间，并返回房间的智能指针管理对象
    room_ptr create_room(uint64_t uid1, uint64_t uid2){
        // 两个用户在游戏大厅中进行对战匹配，匹配成功之后创建房间
        // 1. 校验两个用户是否都在游戏大厅中，只有都在才需要创建房间
        // 2. 创建房间，将用户信息添加到房间中
        // 3. 间房间信息管理起来
        // 4. 返回房间信息

        // 1. 校验两个用户是否都在游戏大厅中，只有都在才需要创建房间
        if(_online_user->is_in_game_hall(uid1) == false){
            DLOG("用户: %lu 不在游戏大厅当中，房间创建失败!", uid1);
            return room_ptr();
        }    
        if(_online_user->is_in_game_hall(uid2) == false){
            DLOG("用户: %lu 不在游戏大厅当中，房间创建失败!", uid2);
            return room_ptr();
        }    

        // {   // 需要保证线程安全的哦
            std::unique_lock<std::mutex> lock(_mutex);
            // 2. 创建房间，将用户信息添加到房间中
            room_ptr rp(new room(_next_rid, _tb_user, _online_user));
            rp->add_white_user(uid1);
            rp->add_black_user(uid2);
            // 3. 间房间信息管理起来
            _rooms.insert(std::make_pair(_next_rid, rp));
            _users.insert(std::make_pair(uid1, _next_rid));
            _users.insert(std::make_pair(uid2, _next_rid));
            _next_rid++;
            // 4. 返回房间信息
            return rp;
        // }
    }

    // 通过房间id获取房间信息
    room_ptr get_room_by_rid(uint64_t rid){
        // 得加锁的哦
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _rooms.find(rid);
        if(it == _rooms.end()){
            return room_ptr();
        }

        return it->second;
    }
    
    // 通过用户id获取房间信息
    room_ptr get_room_by_uid(uint64_t uid){
        // 得加锁的哦
        std::unique_lock<std::mutex> lock(_mutex);
        // 1. 通过用户id获取房间id
        auto uit = _users.find(uid);
        if(uit == _users.end())
            return room_ptr();
        uint64_t rid = uit->second;
        // 得到了房间的id，但是不能通过上面的 room_ptr get_room_by_rid(uint64_t rid) 函数来获取房间信息
        // 因为就死锁了，因为取了 _mutex 这把锁，又去申请锁是会死锁的

        // 2. 通过房间id获取房间信息
        auto rit = _rooms.find(rid);
        if(rit == _rooms.end())
            return room_ptr();

        return rit->second;
    }
    
    // 移除房间
    void remove_room(uint64_t rid){
        // 因为房间信息，是通过shared_ptr在_rooms中进行管理的，因此只要将shared_ptr从_rooms 中移除就可以了
        // 则shared_ptr计数器==0，外界没有对房间信息进行操作保存的情况下就会释放
        // 1. 通过房间id，获取房间信息
        // 2. 通过房间信息，获取房间中所有用户的id
        // 3. 移除房间管理中的用户信息
        // 4. 移除房间管理信息

        // 1. 通过房间id，获取房间信息
        room_ptr rp = get_room_by_rid(rid);
        if(rp.get() == nullptr) return;
        // 2. 通过房间信息，获取房间中所有用户的id
        // --> 一个房间中一般只有两个对战的用户
        uint64_t uid1 = rp->get_white_user();
        uint64_t uid2 = rp->get_black_user();
        // 3. 移除房间管理中的用户信息
        std::unique_lock<std::mutex> lock(_mutex);
        _users.erase(uid1);
        _users.erase(uid2);        
        // 4. 移除房间管理信息
        _rooms.erase(rid);
    }
    
    // 移除房间中的指定用户。如果没有用户了则销毁房间，用户连接断开时被调用
    void remove_room_user(uint64_t uid){
        room_ptr rp = get_room_by_uid(uid);
        if(rp.get() == nullptr) return ;
        // 处理房间中玩家退出动作
        rp->handle_exit(uid);
        // 房间中没有用户了，则销毁房间
        if(rp->player_count() == 0)
            remove_room(rp->id());  // 通过房间id销毁房间
        
        return;
    }
};

#endif

